package pers.mor.tomcat.http;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.LogFactory;
import pers.mor.tomcat.catalina.Context;
import pers.mor.tomcat.filter.ApplicationFilterChain;
import pers.mor.tomcat.servlet.DefaultServlet;
import pers.mor.tomcat.servlet.InvokeServlet;

import javax.servlet.Filter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class HttpProcessor {

    public void execute(Socket socket, Request request, Response response) {
        try {
            String uri = request.getUri();

            if (request.getUri() == null) {
                return;
            }

            Context context = request.getContext();
            if (context == null) {
                handleNoService(socket, response, uri);
                return;
            }

            prepareSession(request, response);

            String servletClassName = context.getServletClassName(uri);

            HttpServlet servlet = null;
            if (servletClassName != null) {
                servlet = InvokeServlet.instance();
            } else{
                servlet = DefaultServlet.instance();
            }

            filteringInterception(context, servlet, request, response);

            if (response.getStatus() == Constant.CODE_200) {
                write(socket, handle200(response));
            } else if (response.getStatus() == Constant.CODE_302) {
                write(socket, handle302(response));
            } else if (response.getStatus() == Constant.CODE_404) {
                write(socket, handle404(response));
            }

        } catch (Exception e) {
            LogFactory.get().error(e);
            response.setContentType("text/html");
            response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
            byte[] responseBytes = handle500(e, response);
            write(socket, responseBytes);
        }
    }

    private static void filteringInterception(Context context, HttpServlet servlet, Request request, Response response) throws IOException, ServletException {
        List<Filter> matchedFilters = context.getMatchedFilters(request.getRequestURI());
        ApplicationFilterChain filterChain = new ApplicationFilterChain(matchedFilters, servlet);
        filterChain.doFilter(request, response);
    }

    private static void prepareSession(Request request, Response response) {
        HttpSession session = SessionManager.getSession(request, response);
        request.setSession(session);
    }

    private static byte[] handleResponse(Response response, String responseHead) {

        byte[] body = response.getBody();

        String encoding = response.getCharacterEncoding();
        String contentType = response.getContentType();
        String cookiesHeader = response.getCookiesHeader();

        if (response.isUseCompression() == true) {
            responseHead = response.compression().useCompression(body, contentType, responseHead);
            body = response.compression().gzipCompression(body);
        }

        String headText = StrUtil.format(responseHead, contentType, encoding, cookiesHeader);

        byte[] head = headText.getBytes(StandardCharsets.UTF_8);

        byte[] responseBytes = new byte[head.length + body.length];
        ArrayUtil.copy(head, 0, responseBytes, 0, head.length);
        ArrayUtil.copy(body, 0, responseBytes, head.length, body.length);

        response.setStatus(0);// 当响应状态码应当为一次性使用，用完即初始化。
        response.getWriter().flush();
        response.getWriter().close();

        return responseBytes;
    }

    private static byte[] handle200(Response response) {
        return handleResponse(response, Constant.response_head_200);
    }

    private static byte[] handle302(Response response) {
        String cookiesHeader = response.getCookiesHeader();
        String redirectPath = response.getRedirectPath();
        String head = StrUtil.format(Constant.response_head_302, null, null, cookiesHeader, redirectPath);
        return head.getBytes(StandardCharsets.UTF_8);
    }

    private static byte[] handle404(Response response) {
        return handleResponse(response, Constant.response_head_404);
    }

    private static byte[] handle500(Exception exception500, Response response) {
        StackTraceElement[] stackTrace = exception500.getStackTrace();
        String eMsg = exception500.getMessage();
        String eStr = exception500.toString();
        StringBuffer strBuff = new StringBuffer();
        for (StackTraceElement stack : stackTrace) {
            strBuff.append(stack.toString() + "\r\n");
        }

        String encoding = response.getCharacterEncoding();
        String contentType = response.getContentType();
        String cookiesHeader = response.getCookiesHeader();

        String headText = StrUtil.format(Constant.response_head_500, contentType, encoding, cookiesHeader);
        String bodyText = StrUtil.format(Constant.textFormat_500, eMsg, eStr, strBuff);

        byte[] head = headText.getBytes(StandardCharsets.UTF_8);
        byte[] body = bodyText.getBytes(StandardCharsets.UTF_8);

        byte[] responseBytes = new byte[head.length + body.length];
        ArrayUtil.copy(head, 0, responseBytes, 0, head.length);
        ArrayUtil.copy(body, 0, responseBytes, head.length, body.length);

        return responseBytes;
    }

    private static void write(Socket socket, byte[] bytes) {
        try{
            OutputStream out = socket.getOutputStream();
            out.write(bytes);
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (!socket.isClosed()) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static void handleNoService(Socket socket, Response response, String uri) {
        String response404 = StrUtil.format(Constant.textFormat_404, uri, uri);
        response.getWriter().print(response404);
        response.setStatus(Constant.CODE_404);
        write(socket, handle404(response));
    }
}
